這篇的起點,是一句很平常的請求:「45MB 的 log file,你可以讀取並分析嗎?」這份 log 之所以累積到這個規模、之所以已經帶有結構化的錯誤分類,是因為大約一週前,系統裡就先加上了一段專門記錄附件寫入資料庫失敗細節的診斷機制(嘗試了什麼、資料庫回什麼錯、比對到哪些既有紀錄)。這份 log 只是靜靜地把真實案例錄了一週,等著被拿出來看。AI 的回答也很平常:「可以,我會用 grep、awk、wc、Python 逐行讀取分段分析,不會整檔塞進對話。」這一步做得又快又好,沒有任何值得寫的協作張力——AI 自己處理得很好,不需要人插手。
但那份 log 分析出來的結果,牽出了一個追了兩輪、猜錯七次的完整偵探故事。這是我目前跟 AI 協作裡,人被迫糾正 AI 次數最多的一次,也是最能說明「AI 給假設、人給業務規則」這種分工到底怎麼運作的一次。
log 裡最大宗的錯誤是 附件下載紀錄寫入失敗,共 493 筆,錯誤訊息是 Oracle 的 ORA-00001(唯一鍵衝突)。業務流程是:承辦人在畫面上按下「接收」完成分文,系統接著背景下載中央單位的附件檔案,寫進 附件下載紀錄 這張表。症狀是:附件檔案下載全部成功,但寫入資料庫這一步,卻大量撞到重複鍵。
假設一:同一案重複分文、但日期不同。 AI 從自己寫的診斷程式註解裡看到線索,判斷是這個方向。我直接反駁:「附件下載紀錄 的 UK 是 縣市別+案號+序號+檔案序號,就算真的同號重複送件,seq 本來就會不同,理論上不該撞號。」AI 立刻收回:「你的質疑完全正確,我先前的假設方向錯了。」
假設二:自動下載還在跑,同時被手動點了「重新下載」。 AI 抓到好幾筆時間重疊的案例。但我指出他看錯了頁面:「這頁雖然也叫 index.ejs,但跟剛剛給你的是不同程式……狀態為下載中時,手動下載的按鈕根本不顯示,承辦人想按也按不了。」AI 查了程式碼確認:下載情形 !== '9' 時,「操作」欄位直接回傳空字串,連 DOM 元素都不存在。這條路徑物理上不可能發生。
假設三:同一案被兩個不同人各自收件。 我提出這個可能性,但這次換 AI 用證據反駁我:「如果是兩個人各自分文同一案,應該會看到兩次獨立的『分文完成→自動下載』,但我查了這 10 個撞號案件,每個 decid 的『開始下載』觸發都只出現 1 次。」
三個假設都被推翻之後,真正的根因才浮現:系統裡其實有三個「重新下載」的入口(清單頁按鈕、詳情頁按鈕、附件檢視頁的「由中央單位重新下載」按鈕),其中附件檢視頁那顆按鈕完全沒有任何防呆機制——沒有 下載情形 檢查、沒有 disabled,承辦人在下載進行中也能點下去,觸發第二次下載。修法是後端加鎖、前端補檢查,寫成 md 檔交給 Claude Code 處理,而且 AI 很老實地在附錄裡標註「還有幾個案件的時間點對不太上,這幾筆的機制可能不一樣,建議分開核對,不要照單全收我這次的結論」。
修改部署隔天,問題又出現了。但這次的樣貌不一樣:不是零星幾筆部分失敗,是同一案的附件 100% 下載成功、緊接著 100% 寫入撞號,而且橫跨 11 個不同案件,模式一致。這代表在這支程式準備寫入之前,已經有另一支程式把這些紀錄整批寫完了。
接下來的追查,AI 又連續猜錯了四次,每一次都被我用一個業務規則或部署事實擋下來:
猜錯之四:分文動作被觸發了兩次,各自產生不同的送件編號。 我直接糾正:「你錯了,分文只執行一次,送件編號 是中央單位給的,等同案件唯一編號,同一案不可能有兩個以上的送件編號。」AI 承認:「這個修正很關鍵,我之前的推論方向整個錯了。」
猜錯之五:範圍縮小到「下載觸發點被呼叫兩次」,懷疑是鎖的競速條件,或者是多 process 部署(PM2 cluster)讓記憶體內的鎖各自失效。 我回得很簡短:「沒有用 PM2,docker 啟動命令是 node bin/www。」單一 process,這條路也斷了。
猜錯之六:懷疑「接收前確認」這個 API(startCheckExistingCase)也會呼叫到同一段初始化邏輯,可能間接觸發第二次。 我這次回得更直接:「我懷疑你想偏了。」並且丟出一個真正切中要害的問題:「下載紀錄是怎麼寫入 收件紀錄.downinfo 的?如果有照我的要求做,應該是行程執行的 console 直接寫入吧?如果是兩支行程,會寫到一起嗎?從 log 上看起來是一個行程中先抓好第一輪,再抓第二輪時全部報錯。」
這個問題把 AI 的思路整個扳正。AI 查了寫入機制後回報:downinfo 欄位是整段覆寫、不是逐行 append,而且如果真有兩支 reciveDownload.js 同時跑,兩邊的 console 輸出應該會交錯出現——但這份 log 完全乾淨,55 筆下載訊息連續、依序、毫無交錯。這不像兩支同名程式在搶跑該有的樣子,比較像是「贏家」根本是一支完全不同的程式,寫 log 的方式也不一樣,所以才不會交錯。
第七次交手,也是最後一次: 我丟出兩個問題——第一,程式裡有沒有直接呼叫 execDownload(一支排程程式,凌晨一點跑,時間跟現在的 log 對不上,所以先問清楚免得白查);第二,一個更犀利的技術追問:「downinfo 寫入是單一行程的 console log,而且是完整寫入沒有分批,那答案應該是不管幾支行程,都是先下載一輪、第二輪才全部報錯——但第一輪顯示成功,能推翻這個現象的,只有『log 顯示的成功其實只代表檔案下載成功,還沒包含資料庫寫入』,這個也幫我確認,因為 Claude Code 改過的版本我沒有確認過這塊。」
AI 去看程式碼確認:我猜對了。改版後的程式碼裡,「下載成功」跟「資料庫寫入成功」是完全分開、不同步的兩個階段——所有檔案的下載動作先跑完,才會進入下一段逐一寫入資料庫。這解釋了「log 裡下載全部顯示成功」跟「接著全部撞號」為什麼會同時出現,卻沒有真正指出「贏家」是誰。
真正抓到根因的,是我自己: 「我大概知道原因了,目前 execDownload.js 的排程還在運作,目前看了 2 件,應該都是 execDownload 已經抓下來的,分文又再抓一次,接收時又認定沒有資料,因此沒有去進行資料確認,就直接硬塞了。」
AI 確認這個解釋完全說得通,而且一次解開了所有先前解釋不了的細節:execDownload.js 是凌晨排程,專門把前一天掛號的案件提前下載寫入,但寫入時 收件紀錄.收件狀態 固定寫死成 "9"(等待狀態)——畫面上完全看不出這個案件其實已經被排程抓過一次。凌晨那支程式用自己獨立的日誌機制,不會跟白天這支的 console 輸出交錯。而且它是整批寫入,所以隔天白天正式分文、reciveDownload.js 重跑一次時,這批檔案早就 100% 存在,自然 100% 撞號。
七次交手拆開來看,AI 每一次的假設都不是亂猜——都有具體的程式碼證據支撐,邏輯也都說得通。但每一次能被推翻,靠的都是我手上握著、AI 拿不到的東西:
附件下載紀錄 的唯一鍵組成,是資料庫設計的既有事實送件編號 是中央單位發的唯一編號、同案不可能重複,是業務規則而最後那個決定性的洞察——凌晨排程已經偷跑一次、收件狀態 卡在 "9" 把這件事藏起來——不是靠邏輯推導出來的,是我對系統裡「還有一支排程在跑」這件事的記憶。AI 手上的資訊完全是靜態的:程式碼、log、你告訴它的事實。它沒辦法無中生有想起「喔對了,還有一支排程」,除非你先想到、先講出來。
修法定案也很乾淨:讓 execDownload.js 下載完成後把 下載情形 改成 "2",白天分文時看到 "2" 就不再重複下載。AI 補了兩個值得注意的細節:一是不要把 下載情形 寫死成 "2",要照顧到部分下載失敗的情況,否則排程半夜只成功一半,白天卻整批跳過,會漏掉沒抓到的附件而沒人發現;二是判斷點要放在觸發下載之前,這樣不管未來從哪裡呼叫,都能一併受惠。
明天要看的是另一個完全不同性質的案子——一場在監控軟體上意外發現的「案外案」:同一套程式碼、22 個縣市裡,只有一個縣市的回應時間是其他縣市的十倍以上。AI 一路被實測數據打臉,最後發現,拖慢畫面的元凶居然有兩個,症狀相似、成因卻完全不同。